package com.kate.mall.bff.wx.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kate.mall.bff.wx.controller.form.QueryGoodsByCateIdForm;
import com.kate.mall.bff.wx.feign.ProductFeignClient;
import com.kate.mall.bff.wx.vo.*;
import com.kate.mall.bff.wx.vo.goods.*;
import com.kate.mall.bff.wx.vo.home.CategoryDetailVO;
import com.kate.mall.bff.wx.vo.home.GoodsPageRelationCategoryVO;
import com.kate.mall.bff.wx.vo.home.GoodsRelationCategoryVO;
import com.kate.mall.common.exception.NDException;
import com.kate.mall.common.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toCollection;

/**
 * @author 新梦想.陈超
 * @version 2021.2
 * @Description: {TODO}
 * @date 2022/12/21 上午10:23
 */

@Service
@Slf4j
public class GoodsService {
    @Resource  //与@Autowired
    private ProductFeignClient productFeignClient;

    @Resource
    private RedisUtils redisUtils;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private ListMap2ListVoUtils2 listMap2ListVoUtils;

    /**
     * 根据商品ID查询商品详情
     * param goodsId    商品id
     *
     * @return
     */

    public R detail(Integer goodsId) throws InterruptedException {
        Map resultMap = new HashMap();
        ThreadPoolExecutor threadPool = ThreadPoolUtils.instance(true);
        try {
            //TODO 1.规格列表---如果该商品是热卖商品，则需要考虑缓存击穿
            Future<R> specificationsListFuture = null;
            if (!isExistCache(goodsId, resultMap, "wx:info:" + goodsId + ":spec", "specificationList")) {
                specificationsListFuture = threadPool.submit(() -> productFeignClient.
                        goodsSpecificationsList(generatorParams(new String[]{"goods_id"}, new Integer[]{goodsId})));
            }
            //TODO 2.团购
            //TODO 3.问题
            //TODO 4.是否为收藏(userHasCollect)
            //TODO 测试数据
            resultMap.put("userHasCollect", 0);
            //TODO 5.商品属性(attribute)
            Future<R> atrributeRelateGoodsListFuture = threadPool.submit(() ->
                    productFeignClient.findAtrributeRelateGoods(goodsId));
            //TODO 6.商品评论列表(comment)
            Future<R> commentRelateGoodsListFuture = threadPool.submit(() ->
                    productFeignClient.getCommentRelateGoods(goodsId));
            //TODO 9.品牌详情(brand)
            Future<R> brandRelateGoodsCallableListFuture = threadPool.submit(() ->
                    productFeignClient.goodsBrandRelateGoods(goodsId));
            //TODO 10.货品(sku)列表信息(productList)
            Future<R> productListFuture = threadPool.submit(() ->
                    productFeignClient.productListRelatedGoods(goodsId));
            //TODO 11.商品详情
            Future<R> goodsInfoCallableFuture = threadPool.submit(() -> productFeignClient.getGoodsInfo(goodsId));
            //TODO 7.是否分享(share)
            //TODO 8.分享的图片(shareImage)
            parseSepecificationFuture(specificationsListFuture, resultMap, "specificationList", SpecifictionVo.class, goodsId);
            parseFuture(atrributeRelateGoodsListFuture, resultMap, "attribute", GoodsAttributeVo.class);
            parseGoodsCommonFuture(commentRelateGoodsListFuture, resultMap, "comment", CommentVo.class);
            parseFuture(brandRelateGoodsCallableListFuture, resultMap, "brand", BrandHomeVo.class);
            parseFuture(productListFuture, resultMap, "productList", GoodsProductVo.class);
            parseGoodsDetailFuture(goodsInfoCallableFuture, resultMap, "", GoodsInfoVo.class);
        } finally {
            threadPool.shutdown();
        }
        return R.ok().put("data", resultMap);
    }

    private void parseGoodsDetailFuture(Future<R> future, Map resultMap, String responseKey, Class voClazz) {
        List<GoodsInfoVo> goodsInfoVoList = (List<GoodsInfoVo>) parseFutureSubject(future, resultMap, responseKey, voClazz, false, false);
        resultMap.put("info", goodsInfoVoList.get(0));
        resultMap.put("share", goodsInfoVoList.get(0).getShareUrl() == null ? false : true);
        resultMap.put("shareImage", goodsInfoVoList.get(0).getShareUrl() == null ? "" : goodsInfoVoList.get(0).getShareUrl());
    }

    private void parseGoodsCommonFuture(Future<R> future, Map resultMap, String responseKey, Class voClazz) {
        List<CommentVo> commentVoList = (List<CommentVo>) parseFutureSubject(future, resultMap, responseKey, voClazz, false, true);
        if (commentVoList != null) {
            GoodsDetailCommentVo goodsDetailCommentVo = new GoodsDetailCommentVo();
            goodsDetailCommentVo.setData(commentVoList);
            try {
                goodsDetailCommentVo.setCount((Integer) ((Map) future.get().get("page")).get("totalCount"));
            } catch (Exception e) {
                e.printStackTrace();
            }
            resultMap.put(responseKey, goodsDetailCommentVo);
        }
    }

    /**
     * Redssion版本：
     * 1.解决单体的缓存击穿问题
     * 2.解决分布式的缓存击穿问题
     * 3.利用的是redis set Nx机制
     * 4.优化 ：
     * 1）设置键的过期时间
     * 2）key必须比与goodsId绑定，降低锁的粒度
     * 3）值采用uuid 不是 固定值
     * 4）del使用 lua脚本
     * 5.看门狗机制：自动续期过期时间 redis官方实现叫RedLock算法 Java对应的版本 Redission
     * 1)设置锁过期时间为1000ms
     * 2)tryLock()尝试获取锁，如果获取不到锁则程序继续执行
     *
     * @param future
     * @param resultMap
     * @param responseKey
     * @param voClazz
     * @param goodsId
     */
    private void parseSepecificationFuture(Future<R> future, Map resultMap, String responseKey, Class voClazz, Integer goodsId) throws InterruptedException {
        //如果是热卖商品，才需要进行缓存击穿的处理
        if (isExistCache(goodsId, resultMap, "wx:info:" + goodsId + ":spec", "specificationList")) {
            return;
        } else {
            //1.获取redisson分布式锁
            RLock rlock = redissonClient.getLock("lock:" + goodsId);
            RedissonRedLock lock = new RedissonRedLock(rlock);
            //2.加锁
            boolean lockStatus = lock.tryLock();//默认超时时间30s，每隔10s钟，查看Redisson实例是否释放锁，
//            boolean lockStatus = lock.tryLock(10, 1000, TimeUnit.MILLISECONDS);//放弃使用看门狗机制
            if (lockStatus) {
                try {
                    //3.获取锁成功后，判断缓存，缓存不存在，则走数据库
                    if (!isExistCache(goodsId, resultMap, "wx:info:" + goodsId + ":spec", "specificationList")) {
                        log.info("从数据库获取规格信息，goodsId:{}", goodsId);

                        List<SpecifictionVo> specifictionsVoList = (List<SpecifictionVo>) parseFutureSubject(future, resultMap, responseKey, voClazz, false, true);
                        if (specifictionsVoList != null) {
                            //specificationVoList转为GoodsDetailspecifictionsVoList
                            //TODO 《获取去重规则的名称》
                            //list<Vo>:[{"id": 2,specification": "规格"},{"id": 3,specification": "颜色"}]}
                            List<SpecifictionVo> distinctSpecifictionVoList = specifictionsVoList.stream().collect(
                                    collectingAndThen(
                                            toCollection(() -> new TreeSet<>(comparing(SpecifictionVo::getSpecification))), ArrayList::new));
                            //需要：{name:规则,valueList:[{value:'1.5'},{value:1.8}]}
                            List<GoodsDetailSpecifictionsVo> goodsDetailspecifictionsVoList = new ArrayList<GoodsDetailSpecifictionsVo>(distinctSpecifictionVoList.size());
                            //list<Vo>:[{"id": 1,specification": "规格"},{"id": 2,specification": "规格"}]}
                            distinctSpecifictionVoList.forEach(dvo -> {
                                GoodsDetailSpecifictionsVo goodsDetailSpecifictionsVo = new GoodsDetailSpecifictionsVo();
                                //设置goodsDetialVo的规则名称
                                goodsDetailSpecifictionsVo.setName(dvo.getSpecification());
                                //设置goodsDetialVo的规则值
                                List<SpecifictionVo> specificationValueList = new ArrayList<>();
                                specifictionsVoList.forEach(vo -> {
                                    if (vo.getSpecification().equals(dvo.getSpecification()))
                                        specificationValueList.add(vo);
                                });
                                goodsDetailSpecifictionsVo.setValueList(specificationValueList);
                                goodsDetailspecifictionsVoList.add(goodsDetailSpecifictionsVo);
                            });
                            resultMap.put(responseKey, goodsDetailspecifictionsVoList);
                            //将结果放到缓存
                            redisUtils.set("wx:info:" + goodsId + ":spec", goodsDetailspecifictionsVoList, 60 * 10 * 24);//缓存一天
                        } else {
                            //解决查询一个不存在的商品，防止缓存穿透，将null值加入缓存中
                            redisUtils.set("wx:info:" + goodsId + ":spec", null, 60 * 10 * 24);//缓存一天
                        }
                    } else {
                        return;
                    }
                }finally {
                    //TODO 第2步：释放锁   1:true 0：false
                    log.info(Thread.currentThread().getId() + "===>释放 redis  分布式锁======");
                    lock.unlock();
                }
            } else {
                //自旋
                Thread.sleep(20);
                //递归
                parseSepecificationFuture(future, resultMap, responseKey, voClazz, goodsId);

            }

        }

    }



    private void parseFuture(Future<R> future, Map resultMap, String responseKey, Class voClazz) {
        parseFutureSubject(future, resultMap, responseKey, voClazz, true, true);
    }

    private List parseFutureSubject(Future<R> future, Map resultMap, String responseKey, Class voClazz, boolean isResponse, boolean isList) {
        R listR = null;
        try {
            listR = future.get();
        } catch (Exception e) {
            throw new NDException("商品详情页接口请求数据异常....");
        }
        List<Map> MapList = isList ? MapUtil.get(MapUtil.get(listR, "page", Map.class), "list", List.class) :
                MapUtil.get(listR, "goods", List.class);

        //裁剪字段两种方案：一、自己写工具类(框架)  二 、mapStruct框架
        //List<map>====>list<vo>
//        List voList = ListMap2ListVoUtils.<NewOrHotGoodsVo>entity2Vo(MapList, voClazz);
        List voList =listMap2ListVoUtils.listMap2ListVo(MapList,voClazz);
        log.info("测试5======>voList:{}",voList);
        if (isResponse && voList != null && voList.size() > 0) {
            resultMap.put(responseKey, voList);
        }
        return voList;
    }

    public Map<String, Object> generatorParams(String[] keys, Object[] values) {
        ConcurrentHashMap<String, Object> params = MapUtil.newConcurrentHashMap();
        if (keys.length != values.length) {
            throw new RuntimeException("键值数量不匹配，参数错误");
        }
        for (int i = 0; i < keys.length; i++) {
            params.put(keys[i], values[i]);
        }
        return params;
    }

    /**
     * 判断的缓存是否存在对应key数据，存在则加入返回结果
     *
     * @param goodsId   商品id
     * @param resultMap 返回结果的Map
     * @ret
     */
    public Boolean isExistCache(Integer goodsId, Map<String, Object> resultMap, String key, String responseKey) {
        if (!ObjectUtil.isEmpty(redisUtils.get(key))) {//缓存中如何存在该
            log.info("==========从【缓存】查询数据 key:{}==========", key);
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                List voList = objectMapper.readValue(redisUtils.get(key), List.class);
//                resultMap.put("specificationList",voList);
                resultMap.put(responseKey, voList);
                return true;
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 统计商品数量
     * @author 新梦想.陈超
     * @date   2023/9/8 11:13 上午
     * @return com.kate.mall.common.utils.R
     * @Version1.0
     **/
    public R homeGoodsCount() {
        final R countR = productFeignClient.homeGoodsCount();
        Integer count = MapUtil.get(countR,"count",Integer.class);
        return R.ok().put("data",count);
    }
    /**
     * 根据父分类id查询该父分类下的所有子分类
     * @author 新梦想.陈超
     * @date   2023/9/8 3:44 下午
     * @param  categoryId 一级分类id
     * @return com.kate.mall.common.utils.R
     * @Version1.0
     **/
    public R categoryDetail(Integer categoryId) {
        Map resultMap = new HashMap();
        Integer parentId=null;
        //1.feign远程调用微服务获取数据
        //1.0.查出当前分类的所有信息
        R cateR = productFeignClient.queryCateById(categoryId);
        //1.1.如果是父分类，则查出该付分类的所有子分类，
        if(MapUtil.get(MapUtil.get(cateR,"category",Map.class),"pid",Integer.class).equals(0)){
            resultMap.put("parentCategory",MapUtil.get(cateR,"category",Map.class));
            parentId = categoryId;
        }else {

            parentId = MapUtil.get(MapUtil.get(cateR, "category", Map.class), "pid", Integer.class);
            R prentCateR = productFeignClient.queryCateById(parentId);
            resultMap.put("parentCategory",MapUtil.get(prentCateR,"category",Map.class));
        }
        //1.2.当前父分类的当前子分类和兄弟子分类
        R childCateListR = productFeignClient.queryChildCateByParentId(parentId);

        //2.将数据封装到VO
        List<Map> listMap = MapUtil.get(MapUtil.get(childCateListR, "page", Map.class), "list", List.class);
//        List<CategoryDetailVO> categoryDetailVO = ListMap2ListVoUtils.entity2Vo(listMap, CategoryDetailVO.class);
        List<CategoryDetailVO> categoryDetailVO = listMap2ListVoUtils.listMap2ListVo(listMap, CategoryDetailVO.class);
        if(parentId==categoryId){
            resultMap.put("currentCategory",categoryDetailVO.get(0));
        }else{
//            CategoryDetailVO currCategory = ListMap2ListVoUtils.map2Vo(MapUtil.get(cateR, "category", Map.class), CategoryDetailVO.class);
            CategoryDetailVO currCategory = listMap2ListVoUtils.map2Vo(MapUtil.get(cateR, "category", Map.class),null,
                    CategoryDetailVO.class);
            resultMap.put("currentCategory",currCategory);
        }

        //返回忽略第0个元素的所有元素集合
//        List<CategoryDetailVO> brotherCategoryVO = categoryDetailVO.stream().skip(1).collect(Collectors.toList());
        resultMap.put("brotherCategory",categoryDetailVO);
        return R.ok().put("data",resultMap);
    }

    /**
     * @author 新梦想.陈超
     * @date   2023/9/11 2:19 下午
     * @param  goodsByCateIdForm 查询分类的商品普通表单
     * @return com.kate.mall.common.utils.R
     * @Version1.0
     **/
    public R queryGoodsByCategoryId(QueryGoodsByCateIdForm goodsByCateIdForm) {
        //1.openFeign远程调用
        R goodsPageR = productFeignClient.queryGoodsListBy2LCategoryId(BeanUtil.beanToMap(goodsByCateIdForm));

        //2.将封装成VO
        List listMap = MapUtil.get(MapUtil.get(goodsPageR, "page", Map.class), "list", List.class);
        //listMap转listVO
        List<GoodsRelationCategoryVO> ListVO = listMap2ListVoUtils.<GoodsRelationCategoryVO>listMap2ListVo(listMap, GoodsRelationCategoryVO.class);

        GoodsPageRelationCategoryVO goodsPageRelationCategoryVO =new GoodsPageRelationCategoryVO();
        goodsPageRelationCategoryVO.setList(ListVO);
        goodsPageRelationCategoryVO.setPage( MapUtil.get(MapUtil.get(goodsPageR, "page", Map.class), "currPage", Integer.class));
        goodsPageRelationCategoryVO.setPages(MapUtil.get(MapUtil.get(goodsPageR, "page", Map.class), "totalPage", Integer.class));
        goodsPageRelationCategoryVO.setTotal(MapUtil.get(MapUtil.get(goodsPageR, "page", Map.class), "totalCount", Integer.class));
        goodsPageRelationCategoryVO.setLimit(MapUtil.get(MapUtil.get(goodsPageR, "page", Map.class), "pageSize", Integer.class));

        return R.ok().put("data",goodsPageRelationCategoryVO);
    }
}
